import { CircleMarker } from './CircleMarker';
import { Path } from './Path';
import * as Util from '../../core/Util';
import { toLatLng } from '../../geo/LatLng';
import { LatLngBounds } from '../../geo/LatLngBounds';
import { Earth } from '../../geo/crs/CRS.Earth';

/*
 * @class Circle
 * @aka L.Circle
 * @inherits CircleMarker
 *
 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
 *
 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
 *
 * @example
 *
 * ```js
 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
 * ```
 */

export var Circle = CircleMarker.extend({
  initialize: function (latlng, options, legacyOptions) {
    if (typeof options === 'number') {
      // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
      options = Util.extend({}, legacyOptions, { radius: options });
    }
    Util.setOptions(this, options);
    this._latlng = toLatLng(latlng);

    if (isNaN(this.options.radius)) {
      throw new Error('Circle radius cannot be NaN');
    }

    // @section
    // @aka Circle options
    // @option radius: Number; Radius of the circle, in meters.
    this._mRadius = this.options.radius;
  },

  // @method setRadius(radius: Number): this
  // Sets the radius of a circle. Units are in meters.
  setRadius: function (radius) {
    this._mRadius = radius;
    return this.redraw();
  },

  // @method getRadius(): Number
  // Returns the current radius of a circle. Units are in meters.
  getRadius: function () {
    return this._mRadius;
  },

  // @method getBounds(): LatLngBounds
  // Returns the `LatLngBounds` of the path.
  getBounds: function () {
    var half = [this._radius, this._radiusY || this._radius];

    return new LatLngBounds(
      this._map.layerPointToLatLng(this._point.subtract(half)),
      this._map.layerPointToLatLng(this._point.add(half)),
    );
  },

  setStyle: Path.prototype.setStyle,

  _project: function () {
    var lng = this._latlng.lng,
      lat = this._latlng.lat,
      map = this._map,
      crs = map.options.crs;

    if (crs.distance === Earth.distance) {
      var d = Math.PI / 180,
        latR = this._mRadius / Earth.R / d,
        top = map.project([lat + latR, lng]),
        bottom = map.project([lat - latR, lng]),
        p = top.add(bottom).divideBy(2),
        lat2 = map.unproject(p).lat,
        lngR =
          Math.acos(
            (Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
              (Math.cos(lat * d) * Math.cos(lat2 * d)),
          ) / d;

      if (isNaN(lngR) || lngR === 0) {
        lngR = latR / Math.cos((Math.PI / 180) * lat); // Fallback for edge case, #2425
      }

      this._point = p.subtract(map.getPixelOrigin());
      this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
      this._radiusY = p.y - top.y;
    } else {
      var latlng2 = crs.unproject(
        crs.project(this._latlng).subtract([this._mRadius, 0]),
      );

      this._point = map.latLngToLayerPoint(this._latlng);
      this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
    }

    this._updateBounds();
  },
});

// @factory L.circle(latlng: LatLng, options?: Circle options)
// Instantiates a circle object given a geographical point, and an options object
// which contains the circle radius.
// @alternative
// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
// Do not use in new applications or plugins.
export function circle(latlng, options, legacyOptions) {
  return new Circle(latlng, options, legacyOptions);
}
